[Swift 3.0] noescape ディレクティブの挙動がデフォルトになった話
noescape とは
noescape は Swift 1.2 で導入された、関数オブジェクトのディレクティブです。@noescape
を関数オブジェクトの直前につけると、関数オブジェクトが同期的に呼び出され、スコープ外にエスケープするタイミングで呼び出されないことを明示的にすることができます。
noescape ディレクティブの挙動の詳細は以下の記事で紹介しています。
Swift 3.0 からは、noescape ディレクティブを付けた場合の挙動が、デフォルトとなりました。これはつまり、引数の関数オブジェクトは通常では同期的に呼び出されるということです。
いままで (Swift 3.0 以前)
HTTP リクエストなどのような非同期な処理は、関数オブジェクトをメソッドに引数で渡し、非同期ハンドラとすることが多いです。例えば以下の例では、someTask
の中で someSyncHandler
を呼び出し、そのハンドラの中で duplicateAndDoubled
を呼び出しています。@noescape
を付けているため、duplicateAndDoubled
の呼び出しには self
を明示的に書く必要がありません。duplicateAndDoubled
メソッドの呼び出し時には、呼び出し元のクラスのインスタンスが必ず存在することが保証されています。
以下は、Swift 2.3 の記法で書いています。
class Hoge { func someSyncHandler(@noescape handler: Void -> Void) { handler() } func someTask() { someSyncHandler { // メソッドのスコープよりもハンドラ関数オブジェクトのライフサイクルは長くない // したがってインスタンスを関数オブジェクトにキャプチャしないため、 // `self` を明示的にしなくてよい let array = duplicateAndDoubled(1) print(array) } } func duplicateAndDoubled(element: Int) -> [Int] { return [element * 2, element * 2] } }
これから (Swift 3.0 以降)
Swift 3.0 からは @noescape
を明示的に付けなくても、@noescape
を付けたときと同じ挙動になります。デフォルトのままで、duplicateAndDoubled
メソッドの呼び出し時に、呼び出し元のクラスのインスタンスが必ず存在することが保証されています。
以下は、Swift 3.0 の記法で書いています。
class Hoge { func someSyncHandler(handler: (Void) -> Void) { handler() } func someTask() { someSyncHandler { // メソッドのスコープよりもハンドラ関数オブジェクトのライフサイクルは長くない // したがってインスタンスを関数オブジェクトにキャプチャしないため、 // `self` を明示的にしなくてよい let array = duplicateAndDoubled(1) print(array) } } func duplicateAndDoubled(_ element: Int) -> [Int] { return [element * 2, element * 2] } }
escaping ディレクティブ
関数オブジェクトを非同期で処理する場合はどうするのかと言うと、新しく追加された escaping ディレクティブを明示的に付ける必要があります。
escaping ディレクティブを付けた場合は、渡した関数オブジェクトの中で self
が関数オブジェクトにキャプチャされることを明示的にするため、self
を付けなければいけません。
class Hoge { func someSyncHandler(handler: @escaping (Void) -> Void) { handler() } func someTask() { someSyncHandler { // self をキャプチャする必要がある let array = self.duplicateAndDoubled(1) print(array) } } func duplicateAndDoubled(_ element: Int) -> [Int] { return [element * 2, element * 2] } }
ちなみに Foundation フレームワークの URLSession
の dataTask:with:completionHandler:
などのような非同期メソッドには @escaping
が指定されています。
open func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask
まとめ
端的にまとめると、次の通りです。
- 同期的な処理のハンドラには
@noescape
は付ける必要はない - 非同期処理のハンドラには
@escaping
を付ける必要がある @escaping
が付いている関数オブジェクトの中では、スコープ外へのアクセスには値のキャプチャが必要
@escaping
は非同期処理で必要となる、と覚えておけば良いでしょう。